package cn.xiangxu.com.webclient_rest_client.proxy;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 王杨帅
 * @create 2018-08-23 15:30
 * @desc 动态代理类的处理类
 **/
@Slf4j
public class MyInvocationHandler implements InvocationHandler {

    /**
     * 服务器信息
     */
    private ServerInfo serverInfo;

    /**
     * 根据服务器信息实例初始化基本请求路径
     * @param serverInfo
     */
    public void init(ServerInfo serverInfo) {
        this.serverInfo = serverInfo;
    }

    /**
     * 代理执行方法：利用代理类实例执行目标类的方法都会进入到invoke方法中执行
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("前置逻辑");

        // 封装方法信息
        MethodInfo methodInfo = new MethodInfo();
        extractUrlAndHttpType(method, methodInfo);
        extractRequstParamsAndBodyParam(method, args, methodInfo);
        extractReturnTypeAndReturnElementType(method, methodInfo);

        System.out.println(methodInfo);

        // 利用RestHandler实现远程调用
        RestHandler restHandler = new WebClientRestHandler();
        Object result = restHandler.restResult(serverInfo, methodInfo);

        log.info("可以根据method参数和args参数获取一些信息，再根据这些信息去执行新的业务逻辑");
        log.info("后置逻辑");
//        return Flux.just("动态代理返回的结果");
//        return Mono.just("动态代理返回的结果");
        return result;
    }

    /**
     * 获取返回类型和响应数据类型
     * @param method
     * @param methodInfo
     */
    private void extractReturnTypeAndReturnElementType(Method method, MethodInfo methodInfo) {
        Class<?> returnType = method.getReturnType();
        boolean isReturnFlux = returnType.isAssignableFrom(Flux.class);

        methodInfo.setReturnFlux(isReturnFlux);

        Class<?> returnElementType = extractElementType(method.getGenericReturnType());

        methodInfo.setReturnElementType(returnElementType);

    }

    /**
     * 获取请求参数和方法体参数
     * @param method
     * @param args
     * @param methodInfo
     */
    private void extractRequstParamsAndBodyParam(Method method, Object[] args, MethodInfo methodInfo) {

        Map<String, Object> params = new HashMap<>();
        methodInfo.setRequestParams(params);

        Parameter[] parameters = method.getParameters();

        for (Integer i = 0; i < parameters.length; i++) {
            PathVariable pathVariable = parameters[i].getAnnotation(PathVariable.class);
            if (pathVariable != null) {
                params.put(pathVariable.value(), args[i]);
            }

            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                methodInfo.setBodyParam((Mono) args[i]);
                Class bodyParamElementType = extractElementType(parameters[i].getParameterizedType());
                methodInfo.setBodyParamEelementType(bodyParamElementType);
            }
        }
    }

    /**
     * 获取泛型类型的元素类型
     * @param parameter
     * @return
     */
    private Class<?> extractElementType(Type parameter) {
        Type type = ((ParameterizedType) parameter).getActualTypeArguments()[0];
        return (Class<?>) type;
    }

    /**
     * 获取请求类型和请求路径
     * @param method
     * @param methodInfo
     */
    private void extractUrlAndHttpType(Method method, MethodInfo methodInfo) {

        Annotation[] annotations = method.getAnnotations();

        for (Annotation annotation : annotations) {

            if (annotation instanceof GetMapping) {
                methodInfo.setHttpUrl(((GetMapping) annotation).value()[0]);
                methodInfo.setHttpMethod(HttpMethod.GET);
            }

            if (annotation instanceof PutMapping) {
                methodInfo.setHttpUrl(((PutMapping) annotation).value()[0]);
                methodInfo.setHttpMethod(HttpMethod.PUT);
            }

            if (annotation instanceof DeleteMapping) {
                methodInfo.setHttpUrl(((DeleteMapping) annotation).value()[0]);
                methodInfo.setHttpMethod(HttpMethod.DELETE);
            }

            if (annotation instanceof PostMapping) {
                methodInfo.setHttpUrl(((PostMapping) annotation).value()[0]);
                methodInfo.setHttpMethod(HttpMethod.POST);
            }


        }

    }
}

